/*
 ********************************************************************************
 *
 * Copyright (c) 2019-2024 Renesas Electronics Corporation and/or its affiliates
 *
 ********************************************************************************
 */

package com.dialog.suotalib.suota;

import android.annotation.SuppressLint;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.util.Log;

import com.dialog.suotalib.global.SuotaLibConfig;
import com.dialog.suotalib.global.SuotaLibLog;
import com.dialog.suotalib.global.SuotaProfile;
import com.dialog.suotalib.utils.MainThread;
import com.dialog.suotalib.utils.SuotaUtil;

import androidx.annotation.NonNull;

import static android.bluetooth.BluetoothGatt.GATT_SUCCESS;
import static android.bluetooth.BluetoothProfile.STATE_CONNECTED;
import static com.dialog.suotalib.global.SuotaProfile.SuotaManagerStatus.CONNECTED;
import static com.dialog.suotalib.global.SuotaProfile.SuotaManagerStatus.DISCONNECTED;

class GattCallback extends BluetoothGattCallback implements Handler.Callback {

    private static final String TAG = "GattCallback";

    private static final int ON_CONNECTION = 0;
    private static final int ON_DISCONNECTION = 1;
    private static final int ON_SERVICES_DISCOVERED = 2;
    private static final int ON_MTU_CHANGED = 3;
    private static final int ON_CHARACTERISTIC_READ = 4;
    private static final int ON_CHARACTERISTIC_WRITE = 5;
    private static final int ON_CHARACTERISTIC_CHANGED = 6;
    private static final int ON_DESCRIPTOR_WRITE = 7;

    private static class CharacteristicValue {

        BluetoothGattCharacteristic characteristic;
        byte[] value;

        CharacteristicValue(BluetoothGattCharacteristic characteristic, byte[] value) {
            this.characteristic = characteristic;
            this.value = value.clone();
        }
    }

    private SuotaManager suotaManager;
    private HandlerThread handlerThread;
    private Handler handler;

    public GattCallback(@NonNull SuotaManager suotaManager) {
        this.suotaManager = suotaManager;
        if (!SuotaLibConfig.GATT_CALLBACK_USE_BINDER_THREAD) {
            handlerThread = new HandlerThread(GattCallback.class.getSimpleName());
            handlerThread.start();
            handler = new Handler(handlerThread.getLooper(), this);
        }
    }

    public void destroy() {
        Log.d(TAG, "Destroy");
        if (!SuotaLibConfig.GATT_CALLBACK_USE_BINDER_THREAD) {
            handler.removeCallbacksAndMessages(null);
            handlerThread.quit();
        }
    }

    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
        if (SuotaLibLog.GATT_OPERATION)
            Log.d(TAG, "onConnectionStateChange: " + newState + " status=" + status);

        if (suotaManager.getGatt() == null) {
            suotaManager.setGatt(gatt);
        }

        if (newState == STATE_CONNECTED) {
            Log.d(TAG, "Connected");
            if (SuotaLibConfig.GATT_CALLBACK_USE_BINDER_THREAD) {
                onConnection(gatt);
            } else {
                handler.obtainMessage(ON_CONNECTION, gatt).sendToTarget();
            }
        } else {
            Log.d(TAG, "Disconnected, reason=" + status);
            if (SuotaLibConfig.GATT_CALLBACK_USE_BINDER_THREAD) {
                onDisconnection();
            } else {
                handler.obtainMessage(ON_DISCONNECTION).sendToTarget();
            }
        }
    }

    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
        if (status == GATT_SUCCESS) {
            Log.d(TAG, "Services discovered");
            if (SuotaLibConfig.GATT_CALLBACK_USE_BINDER_THREAD) {
                onServicesDiscovered(gatt);
            } else {
                handler.obtainMessage(ON_SERVICES_DISCOVERED, gatt).sendToTarget();
            }
        } else {
            Log.e(TAG, "Service discovery failure, status=" + status);
            notifyFailure(SuotaProfile.Errors.SERVICE_DISCOVERY_ERROR);
        }
    }

    @Override
    public void onMtuChanged(BluetoothGatt gatt, final int mtu, int status) {
        if (status == BluetoothGatt.GATT_SUCCESS) {
            Log.d(TAG, "MTU changed to " + mtu);
            if (SuotaLibConfig.GATT_CALLBACK_USE_BINDER_THREAD) {
                onMtuChanged(mtu);
            } else {
                handler.obtainMessage(ON_MTU_CHANGED, mtu).sendToTarget();
            }
        } else {
            Log.e(TAG, "MTU request failure, status=" + status);
            notifyFailure(SuotaProfile.Errors.MTU_REQUEST_FAILED);
        }
    }

    @Override
    public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
        onCharacteristicRead(gatt, characteristic, characteristic.getValue(), status);
    }

    @Override
    public void onCharacteristicRead(@NonNull BluetoothGatt gatt, @NonNull BluetoothGattCharacteristic characteristic, @NonNull byte[] value, int status) {
        if (status == GATT_SUCCESS) {
            if (SuotaLibLog.GATT_OPERATION)
                Log.d(TAG, "onCharacteristicRead: " + characteristic.getUuid() + " " + SuotaUtil.hexArray(value));
            CharacteristicValue characteristicValue = new CharacteristicValue(characteristic, value);
            if (SuotaLibConfig.GATT_CALLBACK_USE_BINDER_THREAD) {
                onCharacteristicRead(characteristicValue);
            } else {
                handler.obtainMessage(ON_CHARACTERISTIC_READ, characteristicValue).sendToTarget();
            }
        } else {
            Log.e(TAG, "Failed to read characteristic: " + characteristic.getUuid() + " status=" + status);
            notifyFailure(SuotaProfile.Errors.GATT_OPERATION_ERROR);
        }
    }

    @Override
    public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
        if (status == GATT_SUCCESS) {
            if (SuotaLibLog.GATT_OPERATION)
                Log.d(TAG, "onCharacteristicWrite: " + characteristic.getUuid());
            if (SuotaLibConfig.GATT_CALLBACK_USE_BINDER_THREAD) {
                onCharacteristicWrite(characteristic);
            } else {
                handler.obtainMessage(ON_CHARACTERISTIC_WRITE, characteristic).sendToTarget();
            }
        } else {
            Log.e(TAG, "Failed to write characteristic: " + characteristic.getUuid() + " status=" + status);
            if (!suotaManager.isRebootSent())
                notifyFailure(SuotaProfile.Errors.GATT_OPERATION_ERROR);
        }
    }

    @Override
    public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
        onCharacteristicChanged(gatt, characteristic, characteristic.getValue());
    }

    @Override
    public void onCharacteristicChanged(@NonNull BluetoothGatt gatt, @NonNull BluetoothGattCharacteristic characteristic, @NonNull byte[] value) {
        if (SuotaLibLog.GATT_OPERATION)
            Log.d(TAG, "onCharacteristicChanged: " + characteristic.getUuid() + " " + SuotaUtil.hexArray(value));
        CharacteristicValue characteristicValue = new CharacteristicValue(characteristic, value);
        if (SuotaLibConfig.GATT_CALLBACK_USE_BINDER_THREAD) {
            onCharacteristicChanged(characteristicValue);
        } else {
            handler.obtainMessage(ON_CHARACTERISTIC_CHANGED, characteristicValue).sendToTarget();
        }
    }

    @Override
    public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
        if (status == GATT_SUCCESS) {
            if (SuotaLibLog.GATT_OPERATION)
                Log.d(TAG, "onDescriptorWrite: " + descriptor.getCharacteristic().getUuid() + " " + descriptor.getUuid());
            if (SuotaLibConfig.GATT_CALLBACK_USE_BINDER_THREAD) {
                onDescriptorWrite(descriptor);
            } else {
                handler.obtainMessage(ON_DESCRIPTOR_WRITE, descriptor).sendToTarget();
            }
        } else {
            Log.e(TAG, "Failed to write descriptor: " + descriptor.getCharacteristic().getUuid() + " " + descriptor.getUuid() + " status=" + status);
            notifyFailure(SuotaProfile.Errors.GATT_OPERATION_ERROR);
        }
    }

    private void notifyFailure(final int value) {
        MainThread.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                suotaManager.getSuotaManagerCallback().onFailure(value);
            }
        });
    }

    private void notifyConnectionStateChanged(final SuotaProfile.SuotaManagerStatus status) {
        MainThread.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                suotaManager.getSuotaManagerCallback().onConnectionStateChange(status);
            }
        });
    }

    @Override
    public boolean handleMessage(Message message) {
        switch (message.what) {
            case ON_CONNECTION:
                onConnection((BluetoothGatt)message.obj);
                return true;
            case ON_DISCONNECTION:
                onDisconnection();
                return true;
            case ON_SERVICES_DISCOVERED:
                onServicesDiscovered((BluetoothGatt)message.obj);
                return true;
            case ON_MTU_CHANGED:
                onMtuChanged((int)message.obj);
                return true;
            case ON_CHARACTERISTIC_READ:
                onCharacteristicRead((CharacteristicValue)message.obj);
                return true;
            case ON_CHARACTERISTIC_WRITE:
                onCharacteristicWrite((BluetoothGattCharacteristic)message.obj);
                return true;
            case ON_CHARACTERISTIC_CHANGED:
                onCharacteristicChanged((CharacteristicValue)message.obj);
                return true;
            case ON_DESCRIPTOR_WRITE:
                onDescriptorWrite((BluetoothGattDescriptor)message.obj);
                return true;
        }
        return false;
    }

    // region Handle Successful Callbacks

    @SuppressLint("MissingPermission")
    private void onConnection(@NonNull BluetoothGatt bluetoothGatt) {
        suotaManager.setState(SuotaManager.ManagerState.CONNECTED);
        notifyConnectionStateChanged(CONNECTED);
        Log.d(TAG, "Discover services");
        bluetoothGatt.discoverServices();
    }

    private void onDisconnection() {
        suotaManager.setState(SuotaManager.ManagerState.DISCONNECTED);
        suotaManager.close();
        notifyConnectionStateChanged(DISCONNECTED);
        suotaManager.checkAsymmetric();
    }

    private void onServicesDiscovered(BluetoothGatt bluetoothGatt) {
        suotaManager.onServicesDiscovered(bluetoothGatt);
    }

    private void onMtuChanged(int mtu) {
        suotaManager.onMtuChanged(mtu);
        suotaManager.dequeueGattOperation();
    }

    private void onCharacteristicRead(CharacteristicValue characteristicValue) {
        suotaManager.onCharacteristicRead(characteristicValue.characteristic, characteristicValue.value);
        suotaManager.dequeueGattOperation();
    }

    private void onCharacteristicWrite(BluetoothGattCharacteristic bluetoothGattCharacteristic) {
        suotaManager.onCharacteristicWrite(bluetoothGattCharacteristic);
        suotaManager.dequeueGattOperation();
    }

    private void onCharacteristicChanged(CharacteristicValue characteristicValue) {
        suotaManager.onCharacteristicChanged(characteristicValue.characteristic, characteristicValue.value);
    }

    private void onDescriptorWrite(BluetoothGattDescriptor bluetoothGattDescriptor) {
        suotaManager.onDescriptorWrite(bluetoothGattDescriptor);
        suotaManager.dequeueGattOperation();
    }

    // endregion
}
